package no.rmz.chez.chess.board;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

import no.rmz.chez.chess.pieces.ChessPiece;
import no.rmz.chez.game.Move;


public final class ChessBoard {


    final static public  String NEWLINE = System.getProperty("line.separator");


    /**
     * This is an 8x8 array containing all the pieces in
     * their start configuration.  It is used to initialize
     * the ChessBoard data structures.
     */
    private static  ChessPiece [][] INITIAL_BOARD_ARRAY =
         new ChessPiece [][] {
            {ChessPiece.WR,  ChessPiece.WN, ChessPiece.WB, ChessPiece.WK, ChessPiece.WQ, ChessPiece.WB, ChessPiece.WN,  ChessPiece.WR},
            {ChessPiece.WP,  ChessPiece.WP, ChessPiece.WP, ChessPiece.WP, ChessPiece.WP, ChessPiece.WP, ChessPiece.WP,  ChessPiece.WP},
            {         null,           null,          null,          null,          null,          null,          null,           null},
            {         null,           null,          null,          null,          null,          null,          null,           null},
            {         null,           null,          null,          null,          null,          null,          null,           null},
            {         null,           null,          null,          null,          null,          null,          null,           null},
            {ChessPiece.BP,  ChessPiece.BP, ChessPiece.BP, ChessPiece.BP, ChessPiece.BP, ChessPiece.BP, ChessPiece.BP,  ChessPiece.BP},
            {ChessPiece.BR,  ChessPiece.BN, ChessPiece.BB, ChessPiece.BK, ChessPiece.BQ, ChessPiece.BB, ChessPiece.BN,  ChessPiece.BR},
        };

    private final static Map<Coord, ChessPiece> INITIAL_BOARD_MAP = getBoardMap(INITIAL_BOARD_ARRAY);

    /**
     * A map mapping coordinates to chesspieces.
     */
    private Map<Coord, ChessPiece> pieces;


    @Override
    public String toString() {
        final String       headfoot = "  a b c d e f g h";

        final StringBuffer      res = new StringBuffer(headfoot).append(NEWLINE);
        for (int y = 0; y < 8; y++) {
            res.append(y).append(" ");
            for (int x = 0 ; x < 8 ; x++) {

                final ChessPiece p = getPieceAt(x, y);
                if  ( p != null ) {
                    // System.out.printf("Found %s at (%d, %d)\n", p.getPrintName(), x, y );
                    res.append(p.getPrintName());
                } else {
                    res.append(" ");
                }
                res.append(" ");
            }
            res.append(y).append(NEWLINE);
        }
        return res.append(headfoot).toString();
    }



    /**
     * Create a new chessboard from an original,
     * copying every piece in their original positions.
     * @param cb
     */
    public ChessBoard(final ChessBoard cb) {
        this(cb.pieces);
    }

    private  ChessBoard(final Map<Coord, ChessPiece> p) {
        pieces = new HashMap<>(p);
    }

    public  ChessBoard(final ChessPiece [][] ba){
        this(getBoardMap(ba));
    }

    /**
     * Get a new chessboard with pieces in their
     * initial positions.
     *
     */
    public ChessBoard() {
        this(getInitialBoardMap());
    }


    private ChessPiece getPieceAt(final int x, final int y) {
        return getPieceAt(Coord.getCoord(x, y));
    }

    public final ChessPiece getPieceAt(final Coord c) {
        assert (     c != null);
        assert (pieces != null);

        return pieces.get(c);
    }

    /**
     * Return true if the game is still ongoing.
     * @return True if the game is ongoing.
     */
    public boolean gameIsOngoing() {
        return true; // XXX Should perhaps check if there is still a king here ;)
    }



    /**
     *  Get all pieces associated with this board.
     * @return
     */
    public final  Map<Coord, ChessPiece> getPiecesMap() {
            assert (pieces != null);
            return pieces;
    }

    public Collection<ChessPiece> getPieces() {
        return getPiecesMap().values();
    }

    public boolean hasTwoKings() {
        int kingCount = 0;
        for (final ChessPiece p : getPieces()) {
            if (p.isKing()) {
                kingCount += 1;
            }
        }
        return kingCount == 2;
    }

    /**
     * Move a piece from a source to a destination.
     * @param source
     * @param dest
     */
    public void applyMove(final Coord source, final Coord dest) {
       getPiecesMap().put(dest, getPieceAt(source));
       getPiecesMap().remove(source);
    }

    public boolean coordIsFree(final Coord c) {
        assert(c != null);
        final ChessPiece p = getPieceAt(c);
        return (p == null);
    }

    /**
     * Get all pieces of a particular color from this board.
     * @param c
     * @return The set of pieces present on the board with a particular color
     */
    public Map<Coord, ChessPiece> getPieces(final Color c) {
        final TreeMap<Coord, ChessPiece> myPieces =
            new TreeMap<>();
        for (final Entry<Coord, ChessPiece> e: getPiecesMap().entrySet()) {
            if (e.getValue().getColor() == c) {
                myPieces.put(e.getKey(), e.getValue());
            }
        }

        return myPieces;
    }




    public final ChessBoard afterMove(final Move m){
        final ChessBoard b = new ChessBoard(this);
        b.applyMove(m);
        return b;
    }

    public void applyMove(final Move m) {
       final Coord      source = m.getSource();
       final ChessPiece     tp;
       if (m.isTransformingMove()) {
           tp = m.getTransformingPiece();
       } else {
           tp = getPieceAt(source);
       }
       getPiecesMap().put(m.getDest(),tp);
       getPiecesMap().remove(source);
    }




    /**
     *
     */

    private static synchronized Map<Coord, ChessPiece> getBoardMap(ChessPiece [][] ba){
        final TreeMap<Coord, ChessPiece> b = new TreeMap<>();
        setPieces(b, ba);
        return b;
    }



    /**
     * Accessor for the INTIAL_BOARD_MAP data.  If it is empty, i
     * it is initialized with values from the INTIIAL_BOARD_ARRAY.
     * @return
     */
    private static synchronized Map<Coord, ChessPiece> getInitialBoardMap() {

        return INITIAL_BOARD_MAP;
    }

    /**
     * Set all of the pieces in the map from the corresponding pieces in
     * the array. Used to initialize a chessboard.
     * @param m
     * @param pieces
     */
    private static void setPieces(
            final Map<Coord,ChessPiece> m,
            final ChessPiece[][] pieces) {
        final int len =   Coord.MAX_BOARD_INDEX - Coord.MIN_BOARD_INDEX + 1;

        assert (pieces.length == len);
        for (int i = Coord.MIN_BOARD_INDEX; i <  Coord.MAX_BOARD_INDEX; i++) {
            assert (pieces[i].length == len);
        }

        final int min = Coord.MIN_BOARD_INDEX;
        final int max = Coord.MAX_BOARD_INDEX;
        for (int y = min; y <= max; y++) {
            for (int x = min; x <= max; x++) {
                final ChessPiece p = pieces[y][x];  // XXX This seems to be right, why?
                if (p != null) {
                    final Coord c = Coord.getCoord(x, y);
                    assert (c != null);
                    assert(c.getX() == x);
                    assert(c.getY() == y);

                    m.put(c, p);
                }
            }
        }
    }
}
